package edu.ccut.saturn.component.impl;

import java.sql.Connection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.jexl.ExpressionFactory;
import org.apache.commons.jexl.JexlContext;

import edu.ccut.saturn.component.DatabaseBroker;
import edu.ccut.saturn.component.ILogger;
import edu.ccut.saturn.component.ILogicContext;
import edu.ccut.saturn.component.IParameterContext;
import edu.ccut.saturn.component.IParameterInfo;
import edu.ccut.saturn.component.IValidate;
import edu.ccut.saturn.component.IValidateInfo;
import edu.ccut.saturn.component.IValidateUseInfo;
import edu.ccut.saturn.component.SaturnComponentException;
import edu.ccut.saturn.component.SaturnLoggerManager;
import edu.ccut.saturn.component.SaturnValidateFailedException;
import edu.ccut.saturn.component.ValidateManager;
import edu.ccut.saturn.component.ValidateResult;

/**
 * {@link ILogicContext} Abstract implementation
 * 
 * @see ILogicContext
 * @author LeslieGu
 * 
 */
public abstract class AbstractSaturnContext implements ILogicContext {

	public String getTransactionType() {
		return this.transactionType;
	}

	private static final String LOGGER_KEY_BUNDLE_RUNTIME = SaturnLoggerManager.LOGGER_KEY_COMPONENT_RUNTIME;

	protected static ILogger logger = SaturnLoggerManager.getInstance()
			.getSaturnLogger(LOGGER_KEY_BUNDLE_RUNTIME);

	private static final String SATURN_EXCEPTION_KEY = "__saturn_exception_key__";

	protected static final String SATURN_CONTEXT = "${context}";

	protected static final String SATURN_SYSTEM_TIME = "${systemTime}";

	protected static final String SATURN_SYSTEM_DATE = "${systemDate}";

	private String id;

	private DatabaseBroker databaseBroker;

	private Map<String, Object> nodeRouter;

	private String transactionType;

	public AbstractSaturnContext(String id) {
		this.id = id;
		this.databaseBroker = new DatabaseBroker();
		this.databaseBroker.setBundleNameByFullKey(id);
		this.nodeRouter = new HashMap<String, Object>();
		this.transactionType = "requireNew";
	}

	public AbstractSaturnContext(String id, Connection con, String type) {
		this.id = id;
		this.databaseBroker = new DatabaseBroker();
		this.databaseBroker.setBundleNameByFullKey(id);
		this.databaseBroker.setConnection(con);
		this.nodeRouter = new HashMap<String, Object>();
		this.transactionType = type;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public abstract Object getValue(String key);

	public abstract void setValue(String key, Object value);

	public abstract Object getValue(IParameterInfo parameterInfo);

	public abstract void setValue(IParameterInfo parameterInfo, Object value);

	public abstract JexlContext getJexlContext();

	public abstract Set<String> getValueKeys(int scope);

	public abstract void cleanScope(int scope);

	public abstract void removeParameterKey(String key, int scope);

	public IParameterContext createParameterContext(
			List<IParameterInfo> parameterInfos)
			throws SaturnValidateFailedException, SaturnComponentException {

		SaturnParameterContext parameterContext = new SaturnParameterContext();
		parameterContext.setLogicContext(this);
		createParameterContext(parameterInfos, parameterContext);
		return parameterContext;
	}

	public IParameterContext createParameterContext(
			List<IParameterInfo> parameterInfos, String type)
			throws SaturnValidateFailedException, SaturnComponentException {

		SaturnParameterContext parameterContext = new SaturnParameterContext(type);
		parameterContext.setLogicContext(this);
		createParameterContext(parameterInfos, parameterContext);
		return parameterContext;
	}

	private void createParameterContext(List<IParameterInfo> parameterInfos,
			SaturnParameterContext parameterContext)
			throws SaturnValidateFailedException, SaturnComponentException {

		Object exception = this.getValue(SATURN_EXCEPTION_KEY);

		if (exception != null) {
			parameterContext.setException((Throwable) exception);
		}

		if (parameterInfos != null) {
			this.validate(parameterInfos);

			for (IParameterInfo parameterInfo : parameterInfos) {
				// TODO: if a SDO type or a Class, create it for using?
				if (IParameterInfo.PASSING_OUT.equals(parameterInfo
						.getPassingType())) {

					parameterContext.addParameter(null, parameterInfo
							.getClassType());

				} else {
					parameterContext.addParameter(this.getValue(parameterInfo),
							parameterInfo.getClassType());
				}

				logger.debug("        param-before : {id : '"
						+ parameterInfo.getId() + "', key : '"
						+ parameterInfo.getValueKey() + "', value : '"
						+ this.getValue(parameterInfo) + "', type : '"
						+ parameterInfo.getClassType() + "', in/out: '"
						+ parameterInfo.getPassingType() + "', process:'"
						+ parameterContext.getLogicContext().getId() + "'}");
			}
		}
		parameterContext.setDatabaseBroker(this.databaseBroker);
	}

	public void refreshContextFromParameterContext(
			List<IParameterInfo> parameterInfos,
			IParameterContext parameterContext) {
        if (parameterInfos != null && parameterContext != null) {
		    this.setValue(SATURN_EXCEPTION_KEY, parameterContext.getException());
			int minParameterNumber = Math.min(parameterContext.getParameters()
					.size(), parameterInfos.size());

			for (int i = 0; i < minParameterNumber; ++i) {
				IParameterInfo parameterInfo = parameterInfos.get(i);

				if (IParameterInfo.PASSING_OUT.equals(parameterInfo
						.getPassingType())
						|| IParameterInfo.PASSING_IN_OUT.equals(parameterInfo
								.getPassingType())) {

					this.setValue(parameterInfo, parameterContext.getParameter(
							i).getValue());
				}

				logger.debug("        param-end : {id : '"
						+ parameterInfo.getId() + "', key : '"
						+ parameterInfo.getValueKey() + "', value : '"
						+ this.getValue(parameterInfo) + "', type : '"
						+ parameterInfo.getClassType() + "', in/out: '"
						+ parameterInfo.getPassingType() + "', process:'"
						+ parameterContext.getLogicContext().getId() + "'}");
			}
		}
	}

	public DatabaseBroker getDatabaseBroker() {
		return this.databaseBroker;
	}

	public boolean isFinalValueKey(String key) {
		return key != null && key.startsWith("'") && key.endsWith("'");
	}

	public boolean isExpression(String key) {
		return key != null && key.startsWith("${") && key.endsWith("}");
	}

	public Object getExpression(String key) {
		String expression = key.substring(2, key.length() - 1);

		try {
			return ExpressionFactory.createExpression(expression).evaluate(
					this.getJexlContext());

		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	private void validate(List<IParameterInfo> parameterInfos)
			throws SaturnValidateFailedException, SaturnComponentException {

		for (IParameterInfo parameterInfo : parameterInfos) {
			IValidateUseInfo validateUseInfo = parameterInfo.getValidateUses();

			if (validateUseInfo != null && validateUseInfo.isValidate()) {
				List<IValidateInfo> validateInfos = validateUseInfo
						.getValidateInfos();

				ValidateResult result = new ValidateResult();

				if (validateInfos != null) {

					for (IValidateInfo validateInfo : validateInfos) {

						if (validateInfo != null) {

							if (IValidateInfo.VALIDATETYPE_ERROR
									.equals(validateInfo.getValidateType())) {

								IValidate validate = ValidateManager
										.getInstance().getValidate(
												validateInfo.getKey());

								if (validate != null) {
									validate.setErrorInfos(validateInfo
											.getErrorInfos());
								}
								Object[] args = this.getArgs(validateInfo
										.getArgs());

								Object value = this.getValue(parameterInfo
										.getValueKey());

								if (validate != null) {
									validate.validate(value, args, result);
								}
							}
						}
					}
				}

				if (!result.isSuccessful()) {
					throw new SaturnValidateFailedException(result);
				}
			}
		}
	}

	private Object[] getArgs(List<String> validateArgs) {

		if (validateArgs != null && !validateArgs.isEmpty()) {
			Object[] args = new Object[validateArgs.size()];
			int i = 0;

			for (String validateArg : validateArgs) {
				args[i++] = this.getValue(validateArg);
			}
			return args;
		}
		return null;
	}

	public void removeExceptionRouter(String id) {
		this.nodeRouter.remove(id);
	}

	public String getNextNodeId(String id) {
		return (String) this.nodeRouter.get(id);
	}

	public void setNodeRouter(String id, String exceptionId) {
		this.nodeRouter.put(id, exceptionId);
	}
}
